package com.springstudy.redis.controller;

import com.springstudy.redis.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.core.*;
import org.springframework.web.bind.annotation.*;

import java.util.*;

/**
 * @author chenrongrong
 * @since 2020/3/12 15:58
 */
@RestController
@RequestMapping(value = "test")
public class TestController {
    //这里需要用自动注入的RedisUtil，不能自己new，否则无法生成RedisTemplate的实例
    @Autowired
    RedisUtil redisUtil;

    //使用RedisTemplate进行操作，默认使用jdk序列化器
    @Autowired
    RedisTemplate redisTemplate = null;

    //使用StringRedisTemplate进行操作，默认使用string序列化器
    @Autowired
    StringRedisTemplate stringRedisTemplate = null;

    /**
     * redis插入数据
     * @param map
     * @return
     */
    @PostMapping(value = "redisSet")
    public Object redisSet(@RequestBody Map<String, Object> map) {
        Boolean result = redisUtil.set("test", map.get("test"));
        if(result == false) {
            return "false";
        }
        return redisUtil.get("test");
    }

    /**
     * redis获取数据
     * @return
     */
    @PostMapping(value = "redisGet")
    public Object redisGet() {
        return redisUtil.get("test");
    }


    /**
     * redis默认jdk序列化器
     *
     * @param map
     * @return
     */
    @PostMapping(value = "jdkTest")
    public Object jdkTest(@RequestBody Map<String, Object> map) {
        redisTemplate.opsForValue().set("int_key", "1");
        //由于采用默认的jdk序列化器，因此实际保存的数据不是整数，无法自增
        //redisTemplate.opsForValue().increment("int_key", 1);
        return redisTemplate.opsForValue().get("int_key");
    }

    /**
     * redis字符串序列化器
     *
     * @param map
     * @return
     */
    @PostMapping(value = "stringTest")
    public Object stringTest(@RequestBody Map<String, Object> map) {
        stringRedisTemplate.opsForValue().set("int", "1");
        //由于采用string序列化器，因此实际保存的数据是整数，可以递增
        stringRedisTemplate.opsForValue().increment("int", 1);
        return stringRedisTemplate.opsForValue().get("int");
    }

    /**
     * redis哈希表
     *
     * @param map
     * @return
     */
    @PostMapping(value = "hashTest")
    public Object hashTest(@RequestBody Map<String, Object> map) {
        redisTemplate.opsForHash().putAll("hash", map);

        Map<Object, Object> returnHash = redisTemplate.opsForHash().entries("hash");

        return returnHash;
    }

    /**
     * redis列表
     *
     * @return
     */
    @PostMapping(value = "listTest")
    public Object listTest() {
        //删除历史数据
        redisTemplate.delete("list1");
        redisTemplate.delete("list2");
        //从左边插入，最后插入的在列表最左边
        redisTemplate.opsForList().leftPushAll("list1", "l1", "l2", "l3", "l4", "l5");
        //从右边插入，最后插入的在列表最右边
        redisTemplate.opsForList().rightPushAll("list2", "l1", "l2", "l3", "l4", "l5");

        BoundListOperations op1 = redisTemplate.boundListOps("list1");
        BoundListOperations op2 = redisTemplate.boundListOps("list2");

        //从列表最左边获取第一个对象，列表中对象删除
        Object out = op1.leftPop();
        //从列表最右边获取第一个对象，列表中对象删除
        Object out2 = op1.rightPop();
        while(out != null) {
            System.out.println(out.toString());
            out = op1.leftPop();
        }
        List ele;
        //获取列表长度
        Long size = op2.size();
        //获取列表左边index为0，右边index为size的列表，index从0开始
        ele = op2.range(0, size);

        System.out.println("list2的长度为：" + op2.size());
        return ele;
    }

    /**
     * redis集合
     *
     * @return
     */
    @PostMapping(value = "setTest")
    public Object setTest() {
        //初始化
        stringRedisTemplate.delete("set1");
        stringRedisTemplate.delete("set2");
        //插入集合，由于集合不允许重复的元素，因此这里v1只会插入一条
        stringRedisTemplate.opsForSet().add("set1", "v1", "v1", "v2", "v3", "v4", "v5");
        stringRedisTemplate.opsForSet().add("set2", "v1", "v3", "v8", "v9", "v10");
        //获取绑定操作对象
        BoundSetOperations boundSetOperations = stringRedisTemplate.boundSetOps("set1");
        //增加元素
        boundSetOperations.add("v6", "v7");
        //删除元素
        boundSetOperations.remove("v2", "v4");
        //获取所有元素
        Set set1 = boundSetOperations.members();
        //获取元素个数
        Long size = boundSetOperations.size();
        //求交集
        Set inter = boundSetOperations.intersect("set2");
        //求交集并用新集合inter_set保存
        boundSetOperations.intersectAndStore("set2", "inter_set");
        //求差集
        Set diff = boundSetOperations.diff("set2");
        //求差集并用新集合diff_set保存
        boundSetOperations.diffAndStore("set2", "diff_set");
        //求并集
        Set union = boundSetOperations.union("set2");
        //求并集并用新集合union_set保存
        boundSetOperations.unionAndStore("set2", "union_set");
        Map<String, Object> map = new HashMap<>();
        map.put("set1", set1);
        map.put("size", size);
        map.put("inter", inter);
        map.put("diff", diff);
        map.put("union", union);

        return map;
    }


    /**
     * redis排序集合
     *
     * @return
     */
    @PostMapping(value = "zsetTest")
    public Object zsetTest() {
        Random random = new Random(1000);
        Set<ZSetOperations.TypedTuple<String>> typedTupleSet = new HashSet<>();
        for(int i=1; i<9; i++) {
            //分数
            double score = random.nextInt(100) * 0.01;
            System.out.println("value" + i + ": " + score);//按照初始化顺序打印
            //创建一个TypedTuple对象，存入值和分数
            ZSetOperations.TypedTuple<String> typedTuple = new DefaultTypedTuple<>("value"+i, score);
            typedTupleSet.add(typedTuple);
        }
        stringRedisTemplate.delete("zset1");
        //往有序集合插入元素
        stringRedisTemplate.opsForZSet().add("zset1", typedTupleSet);
        //绑定有序集合操作
        BoundZSetOperations boundZSetOperations = stringRedisTemplate.boundZSetOps("zset1");
        double newScore = random.nextInt(100) * 0.01;
        System.out.println(newScore);
        boundZSetOperations.add("value10", newScore);
        //按照index获取集合
        Set<String> setValue = boundZSetOperations.range(1, 6);
        //按照分数获取集合
        Set<String> setScore = boundZSetOperations.rangeByScore(0.2, 0.6);
        //定义范围，注意包名
        RedisZSetCommands.Range range = new RedisZSetCommands.Range();
        //大于value3
        range.gt("value3");
        //大于等于
        //range.gte("value3");
        //小于
        //range.lt("value3");
        //根据range的条件按值排序，这里使用的字符串大小
        Set<String> setLex = boundZSetOperations.rangeByLex(range);
        //删除元素
        boundZSetOperations.remove("value9", "value2");
        //获取分数
        Double getScore = boundZSetOperations.score("value8");
        //按照下标区间，同时返回值和分数
        Set<ZSetOperations.TypedTuple<String>> rangeSet = boundZSetOperations.rangeWithScores(1, 6);
        //按照分数区间，同时返回值和分数
        Set<ZSetOperations.TypedTuple<String>> scoreSet = boundZSetOperations.rangeByScoreWithScores(0.1, 0.6);
        //按从大到小排序
        Set<String> reverseSet = boundZSetOperations.reverseRange(1, 6);

        Map<String, Object> map = new HashMap<>();
        map.put("rangeSet", rangeSet);
        map.put("scoreSet", scoreSet);
        map.put("reverseSet", reverseSet);
        return map;
    }

    /**
     * redis事务
     *
     * @return
     */
    @PostMapping(value = "mutiTest")
    public Object mutiTest() {
        redisTemplate.opsForValue().set("key1", "val1");
        List list = (List) redisTemplate.execute(new SessionCallback() {

            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                //监视key1，如果有修改则不执行事务
                redisOperations.watch("key1");

                //开启事务
                redisOperations.multi();

                redisOperations.opsForValue().set("key2", "val22");
                //调用一个会报错的操作
                //redisOperations.opsForValue().increment("key2", 1);
                redisOperations.opsForValue().set("key3", "val33");

                //提交事务，如果监视的key数据有变化则放弃执行
                //在redis事务中可能会有两类错误：
                //1. 令可能排队失败。如，命令的语法可能是错误 (错误的参数个数，错误的命令名字 ...)，或一些重要的环境问题，如，内存不足。
                //2. 当EXEC 调用后 ，一些命令可能执行失败，如，在一个字符串上进行了列表命令的操作。
                //对第一类错误：
                //从 Redis 2.6.5 开始, 在命令排队期间发生错误，Redis会拒绝执行 EXEC，并返回一个错误，然后自动放弃这个事务。
                //在 Redis 2.6.5 之前，EXEC调用后，会执行排队成功的命令，忽略失败的命令。
                //对第二类错误：
                //redis会执行可以成功执行的命令，忽略失败的命令
                return redisOperations.exec();
            }
        });

        return list;
    }

    /**
     * redis流水线
     *
     * @return
     */
    @PostMapping(value = "piplineTest")
    public Object piplineTest() {
        Long start = System.currentTimeMillis();
        List list = (List) redisTemplate.executePipelined(new SessionCallback() {
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                for(int i=1; i<=100000; i++) {
                    redisOperations.opsForValue().set("pipline_" + i, "value_" + i);
                    //这里获取的值为空，因为流水线命令只是进入队列，还没有执行
                    //Object value = redisOperations.opsForValue().get("pipline_" + i);
                    if(i == 10000) {
                        System.out.println("命令进入队列，值为空【null】");
                    }
                }
                return null;
            }
        });
        Long end = System.currentTimeMillis();
        return "总执行时间：" + (end - start) + "毫秒";
    }



}
